﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Me.Prettyprint.Cassandra.Serializers
{
    /// <summary>
    /// The current implementation uses the same method as FluentCassandra to
    /// generate time UUID. We do this to make it easy to transition from
    /// FluentCassandra to Nectar.
    /// 
    /// Note that Guid isn't safe for cross platform applications that want
    /// to read from other languages like python or other languages. It has
    /// been tested with Java and is compatible. Note that when the column
    /// name is a Guid, it is formatted differently if UUID type is used.
    /// For example:
    /// Guid in .Net - ef11f76d-f87b4442-a1b4-3bb4cfd6d878
    /// Guid in Java - ef11f76df87b4442a1b43bb4cfd6d878
    /// 
    /// If you want it to look exactly the same, convert the Guid to a string
    /// and that will insure it works across .Net and Java.
    /// </summary>
    public class GuidUtils
    {
        private static readonly DateTimeOffset GregorianCalendarStart = new DateTimeOffset(1582, 10, 15, 0, 0, 0, TimeSpan.Zero);
        private const int ByteArraySize = 16;
        private const int VariantByte = 8;
        private const int VariantByteMask = 0x3f;
        private const int VariantByteShift = 0x80;
        private const int VersionByte = 7;
        private const int VersionByteMask = 0x0f;
        private const int VersionByteShift = 4; private const byte TimestampByte = 0;
        private const byte GuidClockSequenceByte = 8;
        private const byte NodeByte = 10;
        public enum GuidVersion
        {
            TimeBased = 0x01,
            Reserved = 0x02,
            NameBased = 0x03,
            Random = 0x04
        }
        /// <summary>
        /// For now this is copied from FluentCassandra until I have time to write
        /// a C# native TimeUUID that is exactly the same as Java.
        /// </summary>
        /// <param name="dateTime"></param>
        /// <param name="clockSequence"></param>
        /// <param name="node"></param>
        /// <returns></returns>
        public static Guid GenerateTimeBasedGuid(DateTimeOffset dateTime, byte[] clockSequence, byte[] node)
        {
            if (clockSequence == null)
                throw new ArgumentNullException("clockSequence");

            if (node == null)
                throw new ArgumentNullException("node");

            if (clockSequence.Length != 2)
                throw new ArgumentOutOfRangeException("clockSequence", "The clockSequence must be 2 bytes.");

            if (node.Length != 6)
                throw new ArgumentOutOfRangeException("node", "The node must be 6 bytes.");

            long ticks = (dateTime - GregorianCalendarStart).Ticks;
            byte[] guid = new byte[ByteArraySize];
            byte[] timestamp = BitConverter.GetBytes(ticks);

            // copy node
            Array.Copy(node, 0, guid, NodeByte, Math.Min(6, node.Length));

            // copy clock sequence
            Array.Copy(clockSequence, 0, guid, GuidClockSequenceByte, Math.Min(2, clockSequence.Length));

            // copy timestamp
            Array.Copy(timestamp, 0, guid, TimestampByte, Math.Min(8, timestamp.Length));

            // set the variant
            guid[VariantByte] &= (byte)VariantByteMask;
            guid[VariantByte] |= (byte)VariantByteShift;

            // set the version
            guid[VersionByte] &= (byte)VersionByteMask;
            guid[VersionByte] |= (byte)((byte)GuidVersion.TimeBased << VersionByteShift);

            return new Guid(guid);
        }

    }
}
